#include "stdafx.h"
#include "popup.h"
#include "ini2.h"
#include "otherfunctions.h"
#include "enbitmap.h"
#include "TaskbarNotifier.h"
#include "popupdlg.h"
//#include "UserMsgs.h"
#include <atlimage.h>
#include <string>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define IDT_HIDDEN			0
#define IDT_APPEARING		1
#define IDT_WAITING			2
#define IDT_DISAPPEARING	3
#define TASKBAR_X_TOLERANCE 10
#define TASKBAR_Y_TOLERANCE 10

using namespace std;

//-----------------------------------------------

// Reply buttons coords
#define POINT_REPLY_SENDER        CPoint( 18,  43)
#define POINT_REPLY_SMALLRECT     CPoint( 24,  71)
#define POINT_REPLY_MEDIUMRECT    CPoint( 24, 134)
#define POINT_REPLY_BIGRECT       CPoint(151, 344)

// Reply all buttons coords
#define POINT_REPLYALL_SENDER     CPoint(100,  43)
#define POINT_REPLYALL_SMALLRECT  CPoint(106,  71)
#define POINT_REPLYALL_MEDIUMRECT CPoint(106, 134)
#define POINT_REPLYALL_BIGRECT    CPoint(233, 344)

// Size of the reply and reply all button
SIZE    REPLY_BTN_SIZE            = { 75, 15 };

//-----------------------------------------------

inline bool NearlyEqual(int a, int b, int iEpsilon)
{
	return abs(a - b) < iEpsilon / 2;
}

// CTaskbarNotifier

IMPLEMENT_DYNAMIC(CTaskbarNotifier, CWnd)

BEGIN_MESSAGE_MAP(CTaskbarNotifier, CWnd)
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONDOWN()
	ON_WM_RBUTTONDOWN()
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_SETCURSOR()
	ON_WM_TIMER()
	ON_WM_SYSCOLORCHANGE()
END_MESSAGE_MAP()

CTaskbarNotifier::CTaskbarNotifier() 
 : m_bIsShowingAnim(false)
{
	m_tConfigFileLastModified = -1;
	(void)m_strCaption;
	m_pWndParent = NULL;
	m_bMouseIsOver = FALSE;
	m_hBitmapRegion = NULL;
	m_hCursor = NULL;
	m_crNormalTextColor = RGB(133, 146, 181);
	m_crSelectedTextColor = RGB(10, 36, 106);
	m_nBitmapHeight = 0;
	m_nBitmapWidth = 0;
	m_bBitmapAlpha = false;
	m_dwShowEvents = 0;
	m_dwHideEvents = 0;
	m_nCurrentPosX = 0;
	m_nCurrentPosY = 0;
	m_nCurrentWidth = 0;
	m_nCurrentHeight = 0;
	m_nIncrementShow = 0;
	m_nIncrementHide = 0;
	m_dwTimeToStay = 4000;
	m_dwTimeToShow = 500;
	m_dwTimeToHide = 200;
	m_nTaskbarPlacement = ABE_BOTTOM;
	m_nAnimStatus = IDT_HIDDEN;
	m_rcText.SetRect(0, 0, 0, 0);
	m_rcCloseBtn.SetRect(0, 0, 0, 0);
	m_replyRect = CRect(0,0,0,0);
	m_replyAllRect = CRect(0, 0, 0, 0);
	m_uTextFormat = DT_MODIFYSTRING | DT_WORDBREAK | DT_PATH_ELLIPSIS | DT_END_ELLIPSIS | DT_NOPREFIX;
	m_hCursor = ::LoadCursor(NULL, MAKEINTRESOURCE(32649)); // System Hand cursor
	m_nHistoryPosition = 0;
	m_bTextSelected = FALSE;

	// If running on NT, timer precision is 10 ms, if not timer precision is 50 ms
	OSVERSIONINFO osvi;
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osvi);
	if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
		m_dwTimerPrecision = 10;
	else
		m_dwTimerPrecision = 50;

	m_pfnAlphaBlend = NULL;
	m_hMsImg32Dll = LoadLibrary(_T("MSIMG32.DLL"));
	if (m_hMsImg32Dll) {
		(FARPROC &)m_pfnAlphaBlend = GetProcAddress(m_hMsImg32Dll, "AlphaBlend");
		if (m_pfnAlphaBlend == NULL) {
			FreeLibrary(m_hMsImg32Dll);
			m_hMsImg32Dll = NULL;
		}
	}
	SetTextFont(_T("Impact"), 50, TN_TEXT_NORMAL, TN_TEXT_UNDERLINE);
}

CTaskbarNotifier::~CTaskbarNotifier()
{
	if (m_hMsImg32Dll)
		FreeLibrary(m_hMsImg32Dll);
	while (m_MessageHistory.GetCount() > 0)
		delete (CTaskbarNotifierHistory*)m_MessageHistory.RemoveTail();
}

LRESULT CALLBACK My_AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	return DefWindowProc(hWnd, nMsg, wParam, lParam);
}

int CTaskbarNotifier::Create(CWnd *pWndParent)
{
	ASSERT( pWndParent != NULL );
	m_pWndParent = pWndParent;

	WNDCLASSEX wcx;
	wcx.cbSize = sizeof(wcx);
	// From: http://www.trigeminal.com/usenet/usenet031.asp?1033
	// Subject: If you are using MFC 6.0 or 7.0 and you want to use MSLU...
	// 
	// There is one additional problem that can occur if you are using AfxWndProc (MFC's main, shared window
	// proc wrapper) as an actual wndproc in any of your windows. You see, MFC has code in it so that if AfxWndProc 
	// is called and is told that the wndproc to follow up with is AfxWndProc, it notices that it is being asked to 
	// call itself and forwards for DefWindowProc instead.
	// 
	// Unfortunately, MSLU breaks this code by having its own proc be the one that shows up. MFC has no way of 
	// detecting this case so it calls the MSLU proc which calls AfxWndProc which calls the MSLU proc, etc., until 
	// the stack overflows. By using either DefWindowProc or your own proc yourself, you avoid the stack overflow.
	bool g_bUnicoWS = true; //false;
	if (g_bUnicoWS)
		wcx.lpfnWndProc = My_AfxWndProc;
	else
		wcx.lpfnWndProc = AfxWndProc;
	static const TCHAR s_szClassName[] = _T("ePopup_TaskbarNotifierWndClass");
	wcx.style = CS_DBLCLKS | CS_SAVEBITS;
	wcx.cbClsExtra = 0;
	wcx.cbWndExtra = 0;
	wcx.hInstance = AfxGetInstanceHandle();
	wcx.hIcon = NULL;
	wcx.hCursor = LoadCursor(NULL,IDC_ARROW);
	wcx.hbrBackground=::GetSysColorBrush(COLOR_WINDOW);
	wcx.lpszMenuName = NULL;
	wcx.lpszClassName = s_szClassName;
	wcx.hIconSm = NULL;
	RegisterClassEx(&wcx);

	return CreateEx(WS_EX_TOPMOST, s_szClassName, NULL, WS_POPUP, 0, 0, 0, 0, pWndParent->m_hWnd, NULL);
}

void CTaskbarNotifier::OnSysColorChange()
{
	CWnd::OnSysColorChange();
}

bool CTaskbarNotifier::isReady()
{
	return (m_nAnimStatus == IDT_HIDDEN);
}
void CTaskbarNotifier::SetPopupImage(LPCTSTR strBmpFilePath)
{
	CEnBitmap imgTaskbar;
	imgTaskbar.LoadImage(strBmpFilePath, (COLORREF)0);
	SetBitmap(&imgTaskbar, 255, 0, 255);
}

void CTaskbarNotifier::SetPopupImage(int _rcId)
{
	CEnBitmap imgTaskbar;
	imgTaskbar.LoadImage(_rcId, _T("GIF"));
	SetBitmap(&imgTaskbar, 255, 0, 255);
}

void CTaskbarNotifier::SetTextFont(LPCTSTR pszFont, int nSize, int nNormalStyle, int nSelectedStyle)
{
	LOGFONT lf;
	m_fontNormal.DeleteObject();
	CreatePointFont(m_fontNormal, nSize, pszFont);
	m_fontNormal.GetLogFont(&lf);

	// We  set the Font of the unselected ITEM
	if (nNormalStyle & TN_TEXT_BOLD)
		lf.lfWeight = FW_BOLD;
	else
		lf.lfWeight = FW_NORMAL;

	if (nNormalStyle & TN_TEXT_ITALIC)
		lf.lfItalic = TRUE;
	else
		lf.lfItalic = FALSE;

	if (nNormalStyle & TN_TEXT_UNDERLINE)
		lf.lfUnderline = TRUE;
	else
		lf.lfUnderline = FALSE;

	m_fontNormal.DeleteObject();
	m_fontNormal.CreateFontIndirect(&lf);

	// We set the Font of the selected ITEM
	if (nSelectedStyle & TN_TEXT_BOLD)
		lf.lfWeight = FW_BOLD;
	else
		lf.lfWeight = FW_NORMAL;

	if (nSelectedStyle & TN_TEXT_ITALIC)
		lf.lfItalic = TRUE;
	else
		lf.lfItalic = FALSE;

	if (nSelectedStyle & TN_TEXT_UNDERLINE)
		lf.lfUnderline = TRUE;
	else
		lf.lfUnderline = FALSE;

	m_fontSelected.DeleteObject();
	m_fontSelected.CreateFontIndirect(&lf);
}

void CTaskbarNotifier::SetTextDefaultFont()
{
	LOGFONT lf;
	AfxGetMainWnd()->GetFont()->GetLogFont(&lf);
	m_fontNormal.DeleteObject();
	m_fontSelected.DeleteObject();
	m_fontNormal.CreateFontIndirect(&lf);
	lf.lfUnderline = TRUE;
	m_fontSelected.CreateFontIndirect(&lf);
}

void CTaskbarNotifier::SetTextColor(COLORREF crNormalTextColor, COLORREF crSelectedTextColor)
{
	m_crNormalTextColor = crNormalTextColor;
	m_crSelectedTextColor = crSelectedTextColor;
	RedrawWindow(&m_rcText);
}

void CTaskbarNotifier::SetTextRect(RECT rcText)
{
	m_rcText = rcText;
}


void CTaskbarNotifier::SetTextFormat(UINT uTextFormat)
{
	m_uTextFormat = uTextFormat;
}

BOOL CTaskbarNotifier::SetBitmap(UINT nBitmapID, int red, int green, int blue)
{
	m_bitmapBackground.DeleteObject();
	if (!m_bitmapBackground.LoadBitmap(nBitmapID))
		return FALSE;

	BITMAP bm;
	m_bitmapBackground.GetBitmap(&bm);
	m_nBitmapWidth = bm.bmWidth;
	m_nBitmapHeight = bm.bmHeight;
	m_bBitmapAlpha = false;

	if (red != -1 && green != -1 && blue != -1)
	{
		// No need to delete the HRGN,	SetWindowRgn() owns it after being called
		m_hBitmapRegion = CreateRgnFromBitmap(m_bitmapBackground, RGB(red, green, blue));
		SetWindowRgn(m_hBitmapRegion, TRUE);
	}

	if (m_nBitmapWidth == 0 || m_nBitmapHeight == 0){
		ASSERT( false );
		return FALSE;
	}
	else
		return TRUE;
}

BOOL CTaskbarNotifier::SetBitmap(CBitmap* pBitmap, int red, int green, int blue)
{
	m_bitmapBackground.DeleteObject();
	if (!m_bitmapBackground.Attach(pBitmap->Detach()))
		return FALSE;

	BITMAP bm;
	m_bitmapBackground.GetBitmap(&bm);
	m_nBitmapWidth = bm.bmWidth;
	m_nBitmapHeight = bm.bmHeight;
	m_bBitmapAlpha = false;

	if (red != -1 && green != -1 && blue != -1)
	{
		// No need to delete the HRGN,	SetWindowRgn() owns it after being called
		m_hBitmapRegion = CreateRgnFromBitmap(m_bitmapBackground, RGB(red, green, blue));
		SetWindowRgn(m_hBitmapRegion, TRUE);
	}

	return TRUE;
}

BOOL CTaskbarNotifier::SetBitmap(LPCTSTR pszFileName, int red, int green, int blue)
{
	if (pszFileName == NULL || pszFileName[0] == _T('\0'))
		return FALSE;
	CEnBitmap Bitmap;
	if (!Bitmap.LoadImage(pszFileName))
		return FALSE;
	m_bitmapBackground.DeleteObject();
	if (!m_bitmapBackground.Attach(Bitmap.Detach()))
		return FALSE;

	BITMAP bm;
	m_bitmapBackground.GetBitmap(&bm);
	m_nBitmapWidth = bm.bmWidth;
	m_nBitmapHeight = bm.bmHeight;
	m_bBitmapAlpha = false;

	if (red != -1 && green != -1 && blue != -1)
	{
		// No need to delete the HRGN,	SetWindowRgn() owns it after being called
		m_hBitmapRegion = CreateRgnFromBitmap(m_bitmapBackground, RGB(red, green, blue));
		SetWindowRgn(m_hBitmapRegion, TRUE);
	}

	return TRUE;
}

void CTaskbarNotifier::setupMessage(LPCTSTR message)
{
	int _rcID;
	string _splittedMessage;

	if (message != NULL)
	{
		int _nbRows = splitString(MFCStringToSTLString(CString(message)), 30, _splittedMessage);

		if (_nbRows <= 2)
		{
			_rcID = IDR_TEXT_RECT_SENDER;
			SetTextRect(CRect(10,10,190,40));
			m_replyRect    = CRect(POINT_REPLY_SENDER, REPLY_BTN_SIZE);
			m_replyAllRect = CRect(POINT_REPLYALL_SENDER, REPLY_BTN_SIZE);
		}
		else if (_nbRows <= 4)
		{
			_rcID = IDR_TEXT_RECT;
			SetTextRect(CRect(10,10,190,80));
			m_replyRect    = CRect(POINT_REPLY_SMALLRECT, REPLY_BTN_SIZE);
			m_replyAllRect = CRect(POINT_REPLYALL_SMALLRECT, REPLY_BTN_SIZE);
		}
		else if (_nbRows <= 10)
		{
			_rcID = IDR_TEXT_RECT_BIG;
			SetTextRect(CRect(10,10,190,130));
			m_replyRect    = CRect(POINT_REPLY_MEDIUMRECT, REPLY_BTN_SIZE);
			m_replyAllRect = CRect(POINT_REPLYALL_MEDIUMRECT, REPLY_BTN_SIZE);
		}
		else
		{
			_rcID = IDR_TEXT_RECT_EXTRA_BIG;
			SetTextRect(CRect(10,10,320,400));
			m_replyRect    = CRect(POINT_REPLY_BIGRECT, REPLY_BTN_SIZE);
			m_replyAllRect = CRect(POINT_REPLYALL_BIGRECT, REPLY_BTN_SIZE);
		}
		SetPopupImage(_rcID);
		SetTextColor(RGB(0,0,0), RGB(0,0,0));

		// Setup message text
		m_strCaption = message;
	}
}
void CTaskbarNotifier::Show(LPCTSTR message, Direction direction, UINT shiftValue, bool p_modal)
{
	m_modal = p_modal;

	// Setup background of image depending on image size
	setupMessage(message);
	
	if (m_nBitmapHeight == 0 || m_nBitmapWidth == 0) { 
		ASSERT( false );
		return;
	}

	// Compute popup...
	startAnimation(direction, shiftValue);

	// Set the popup at the base position
	SetWindowPos(&wndTopMost, m_nCurrentPosX, m_nCurrentPosY, m_nCurrentWidth, m_nCurrentHeight, SWP_NOACTIVATE);
	// and show it!
	RedrawWindow(&m_rcText);
}


void CTaskbarNotifier::Hide()
{
	switch (m_nAnimStatus)
	{
		case IDT_APPEARING:
			KillTimer(IDT_APPEARING);
			break;
		case IDT_WAITING:
			KillTimer(IDT_WAITING);
			break;
		case IDT_DISAPPEARING:
			KillTimer(IDT_DISAPPEARING);
			break;
	}
	MoveWindow(0, 0, 0, 0);
	ShowWindow(SW_HIDE);
	m_nAnimStatus = IDT_HIDDEN;
}

void CTaskbarNotifier::startAnimation(Direction direction, UINT shiftValue)
{
	UINT nEvents;
	UINT nScreenWidth;
	UINT nScreenHeight;
	CRect rcTaskbar;

	nScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
	nScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
	HWND hWndTaskbar = ::FindWindow(_T("Shell_TrayWnd"), 0);
	ASSERT(hWndTaskbar != NULL);
	::GetWindowRect(hWndTaskbar, &rcTaskbar);

	switch (direction)
	{
		case RIGHT_TO_LEFT: 
			m_nTaskbarPlacement = ABE_RIGHT;
			m_nImageSize = m_nBitmapWidth;
			break;
		case LEFT_TO_RIGHT: 
			m_nTaskbarPlacement = ABE_LEFT;    
			m_nImageSize = m_nBitmapWidth;
			break;
		case UP_TO_DOWN:
			m_nImageSize = m_nBitmapHeight;
			m_nTaskbarPlacement = ABE_TOP;
			break;
		case DOWN_TO_UP:
			m_nImageSize = m_nBitmapHeight;
			m_nTaskbarPlacement = ABE_BOTTOM;
			break;
		default:
			AfxMessageBox(_T("Failed to init notifier!"));
			return;
	}

	// We calculate the pixel increment and the timer value for the showing animation
	// For transparent bitmaps, all animations are disabled.
	DWORD dwTimeToShow = m_bBitmapAlpha ? 0 : m_dwTimeToShow;
	if (dwTimeToShow > m_dwTimerPrecision) {
		nEvents = max(min((dwTimeToShow / m_dwTimerPrecision) / 2, m_nImageSize), 1); //<<-- enkeyDEV(Ottavio84) -Reduced frames of a half-
		m_dwShowEvents = dwTimeToShow / nEvents;
		m_nIncrementShow = m_nImageSize / nEvents;
	}
	else {
		m_dwShowEvents = m_dwTimerPrecision;
		m_nIncrementShow = m_nImageSize;
	}

	// We calculate the pixel increment and the timer value for the hiding animation
	// For transparent bitmaps, all animations are disabled.
	DWORD dwTimeToHide = m_bBitmapAlpha ? 0 : m_dwTimeToHide;
	if (dwTimeToHide > m_dwTimerPrecision) {
		nEvents = max(min((dwTimeToHide / m_dwTimerPrecision / 2), m_nImageSize), 1); //<<-- enkeyDEV(Ottavio84) -Reduced frames of a half-
		m_dwHideEvents = dwTimeToHide / nEvents;
		m_nIncrementHide = m_nImageSize / nEvents;
	}
	else {
		m_dwShowEvents = m_dwTimerPrecision;
		m_nIncrementHide = m_nImageSize;
	}

	// Compute init values for the animation
	switch (m_nAnimStatus)
	{
	case IDT_HIDDEN:
		m_bIsShowingAnim = true;
		if (m_nTaskbarPlacement == ABE_RIGHT)
		{
			m_nCurrentPosX = rcTaskbar.right;
			m_nCurrentPosY = rcTaskbar.top - m_nBitmapHeight - shiftValue;
			m_nCurrentWidth = 0;
			m_nCurrentHeight = m_nBitmapHeight;
		}
		else if (m_nTaskbarPlacement == ABE_LEFT)
		{
			m_nCurrentPosX = rcTaskbar.right;
			m_nCurrentPosY = rcTaskbar.bottom - m_nBitmapHeight;
			m_nCurrentWidth = 0;
			m_nCurrentHeight = m_nBitmapHeight;
		}
		else if (m_nTaskbarPlacement == ABE_TOP)
		{
			m_nCurrentPosX = rcTaskbar.right - m_nBitmapWidth;
			m_nCurrentPosY = rcTaskbar.bottom;
			m_nCurrentWidth = m_nBitmapWidth;
			m_nCurrentHeight = 0;
		}
		else
		{
			// Taskbar is on the bottom or Invisible
			m_nCurrentPosX = rcTaskbar.right - m_nBitmapWidth;
			m_nCurrentPosY = rcTaskbar.top;
			m_nCurrentWidth = m_nBitmapWidth;
			m_nCurrentHeight = 0;
		}
		ShowWindow(SW_SHOWNOACTIVATE);
		SetTimer(IDT_APPEARING, m_dwShowEvents, NULL);
		break;

	case IDT_APPEARING:
		RedrawWindow(&m_rcText);
		break;

	case IDT_WAITING:
		RedrawWindow(&m_rcText);
		KillTimer(IDT_WAITING);
		SetTimer(IDT_WAITING, m_dwTimeToStay, NULL);
		break;

	case IDT_DISAPPEARING:
		KillTimer(IDT_DISAPPEARING);
		SetTimer(IDT_WAITING, m_dwTimeToStay, NULL);
		if (m_nTaskbarPlacement == ABE_RIGHT)
		{
			m_nCurrentPosX = rcTaskbar.left - m_nBitmapWidth;
			m_nCurrentWidth = m_nBitmapWidth;
		}
		else if (m_nTaskbarPlacement == ABE_LEFT)
		{
			m_nCurrentPosX = rcTaskbar.right;
			m_nCurrentWidth = m_nBitmapWidth;
		}
		else if (m_nTaskbarPlacement == ABE_TOP)
		{
			m_nCurrentPosY = rcTaskbar.bottom;
			m_nCurrentHeight = m_nBitmapHeight;
		}
		else
		{
			m_nCurrentPosY = rcTaskbar.top - m_nBitmapHeight;
			m_nCurrentHeight = m_nBitmapHeight;
			m_bIsShowingAnim = false;
		}
		break;
	}
}

HRGN CTaskbarNotifier::CreateRgnFromBitmap(HBITMAP hBmp, COLORREF color)
{
	if (hBmp == NULL)
		return NULL;

	CDC* pDC = GetDC();
	if (pDC == NULL)
		return NULL;

	BITMAP bm;
	GetObject(hBmp, sizeof(bm), &bm);

	ASSERT( !m_bBitmapAlpha );
	const BYTE *pBitmapBits = NULL;
	if (bm.bmBitsPixel == 32 && m_pfnAlphaBlend)
	{
		DWORD dwBitmapBitsSize = GetBitmapBits(hBmp, 0, NULL);
		if (dwBitmapBitsSize)
		{
			pBitmapBits = (BYTE *)malloc(dwBitmapBitsSize);
			if (pBitmapBits)
			{
				if (GetBitmapBits(hBmp, dwBitmapBitsSize, (LPVOID)pBitmapBits) == (LONG)dwBitmapBitsSize)
				{
					const BYTE *pLine = pBitmapBits;
					int iLines = bm.bmHeight;
					while (!m_bBitmapAlpha && iLines-- > 0)
					{
						const DWORD *pdwPixel = (const DWORD *)pLine;
						for (int x = 0; x < bm.bmWidth; x++) {
							if (*pdwPixel++ & 0xFF000000) {
								m_bBitmapAlpha = true;
								break;
							}
						}
						pLine += bm.bmWidthBytes;
					}
				}
				if (!m_bBitmapAlpha)
				{
					free((void*)pBitmapBits);
					pBitmapBits = NULL;
				}
			}
		}
	}

	CDC dcBmp;
	dcBmp.CreateCompatibleDC(pDC);
	HGDIOBJ hOldBmp = dcBmp.SelectObject(hBmp);
	HRGN hRgn = NULL;

	// allocate memory for region data
	const DWORD MAXBUF = 40;	// size of one block in RECTs (i.e. MAXBUF*sizeof(RECT) in bytes)
	DWORD cBlocks = 0;			// number of allocated blocks
	RGNDATAHEADER *pRgnData = (RGNDATAHEADER *)calloc(sizeof(RGNDATAHEADER) + ++cBlocks * MAXBUF * sizeof(RECT), 1);
	if (pRgnData)
	{
		// fill it by default
		pRgnData->dwSize = sizeof(RGNDATAHEADER);
		pRgnData->iType	= RDH_RECTANGLES;
		pRgnData->nCount = 0;

		INT iFirstXPos = 0;		// left position of current scan line where mask was found
		bool bWasFirst = false;	// set when mask was found in current scan line

		const BYTE *pBitmapLine = pBitmapBits != NULL ? pBitmapBits + bm.bmWidthBytes * (bm.bmHeight - 1) : NULL;
		for (int y = 0; pRgnData != NULL && y < bm.bmHeight; y++)
		{
			for (int x = 0; x < bm.bmWidth; x++)
			{
				// get color
				bool bIsMask;
				if (pBitmapLine)
					bIsMask = ((((const DWORD *)pBitmapLine)[x] & 0xFF000000) != 0x00000000);
				else
					bIsMask = (dcBmp.GetPixel(x, bm.bmHeight - y - 1) != color);

				// place part of scan line as RECT region if transparent color found after mask color or
				// mask color found at the end of mask image
				if (bWasFirst && ((bIsMask && (x == bm.bmWidth - 1)) || (bIsMask ^ (x < bm.bmWidth))))
				{
					// get offset to RECT array if RGNDATA buffer
					LPRECT pRects = (LPRECT)(pRgnData + 1);

					// save current RECT
					pRects[pRgnData->nCount++] = CRect(iFirstXPos, bm.bmHeight - y - 1, x + (x == bm.bmWidth - 1), bm.bmHeight - y);

					// if buffer full reallocate it
					if (pRgnData->nCount >= cBlocks * MAXBUF) {
						RGNDATAHEADER *pNewRgnData = (RGNDATAHEADER *)realloc(pRgnData, sizeof(RGNDATAHEADER) + ++cBlocks * MAXBUF * sizeof(RECT));
						if (pNewRgnData == NULL) {
							free(pRgnData);
							pRgnData = NULL;
							break;
						}
						pRgnData = pNewRgnData;
					}
					bWasFirst = false;
				}
				else if (!bWasFirst && bIsMask)
				{	
					iFirstXPos = x;
					bWasFirst = true;
				}
			}
			if (pBitmapBits)
				pBitmapLine -= bm.bmWidthBytes;
		}

		if (pRgnData)
		{
			// Create region
			// WinNT: 'ExtCreateRegion' returns NULL (by Fable@aramszu.net)
			hRgn = CreateRectRgn(0, 0, 0, 0);
			if (hRgn)
			{
				LPCRECT pRects = (LPRECT)(pRgnData + 1);
				for (DWORD i = 0; i < pRgnData->nCount; i++)
				{
					HRGN hr = CreateRectRgn(pRects[i].left, pRects[i].top, pRects[i].right, pRects[i].bottom);
					VERIFY( CombineRgn(hRgn, hRgn, hr, RGN_OR) != ERROR );
					if (hr)
						DeleteObject(hr);
				}
			}

			free(pRgnData);
		}
	}

	dcBmp.SelectObject(hOldBmp);
	dcBmp.DeleteDC();
	free((void*)pBitmapBits);
	ReleaseDC(pDC);
	return hRgn;
}

void CTaskbarNotifier::OnMouseMove(UINT nFlags, CPoint point)
{
	TRACKMOUSEEVENT tme;
	tme.cbSize = sizeof(tme);
	tme.dwFlags = TME_LEAVE | TME_HOVER;
	tme.hwndTrack = m_hWnd;
	tme.dwHoverTime = 1;

	// We Tell Windows we want to receive WM_MOUSEHOVER and WM_MOUSELEAVE
	::_TrackMouseEvent(&tme);

	CWnd::OnMouseMove(nFlags, point);
}

void CTaskbarNotifier::addSyncNotifier(CTaskbarNotifier *p_syncNofifier)
{
	m_syncNotifiers.push_back(p_syncNofifier);
}

void CTaskbarNotifier::OnLButtonDown(UINT /*nFlags*/, CPoint point)
{
	// Display reply dialog if a button has been clicked
	if (m_replyRect.PtInRect(point))
	{
		m_pWndParent->PostMessage(UM_REPLY_BTN_CLICKED);
	}
	else if (m_replyAllRect.PtInRect(point))
	{
		m_pWndParent->PostMessage(UM_REPLYALL_BTN_CLICKED);
	}

	// Hide all active notifiers
	Hide();
	vector<CTaskbarNotifier*>::iterator _it;
	for (_it = m_syncNotifiers.begin(); _it != m_syncNotifiers.end(); _it++)
	{
		(*_it)->Hide();
	}
}

BOOL CTaskbarNotifier::OnEraseBkgnd(CDC* pDC)
{
	CDC memDC;
	memDC.CreateCompatibleDC(pDC);
	CBitmap *pOldBitmap = memDC.SelectObject(&m_bitmapBackground);
	if (m_bBitmapAlpha && m_pfnAlphaBlend) {
		static const BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
		(*m_pfnAlphaBlend)(pDC->m_hDC, 0, 0, m_nCurrentWidth, m_nCurrentHeight, 
			               memDC.m_hDC, 0, 0, m_nCurrentWidth, m_nCurrentHeight, bf);
	}
	else {
		pDC->BitBlt(0, 0, m_nCurrentWidth, m_nCurrentHeight, &memDC, 0, 0, SRCCOPY);
	}
	memDC.SelectObject(pOldBitmap);

	return TRUE;
}

void CTaskbarNotifier::OnPaint()
{
	CPaintDC dc(this);
	CFont* pOldFont;
	if (m_bMouseIsOver)
	{
		if (m_rcText.PtInRect(m_ptMousePosition))
		{
			m_bTextSelected = TRUE;
			dc.SetTextColor(m_crSelectedTextColor);
			pOldFont = dc.SelectObject(&m_fontSelected);
		}
		else
		{
			m_bTextSelected = FALSE;
			dc.SetTextColor(m_crNormalTextColor);
			pOldFont = dc.SelectObject(&m_fontNormal);
		}
	}
	else
	{
		dc.SetTextColor(m_crNormalTextColor);
		pOldFont = dc.SelectObject(&m_fontNormal);
	}

	dc.SetBkMode(TRANSPARENT);
	dc.DrawText(m_strCaption, m_strCaption.GetLength(), m_rcText, m_uTextFormat);

	dc.SelectObject(pOldFont);
}

BOOL CTaskbarNotifier::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	if (nHitTest == HTCLIENT)
	{
		if (m_rcCloseBtn.PtInRect(m_ptMousePosition) ||
			m_rcHistoryBtn.PtInRect(m_ptMousePosition) ||
			m_rcText.PtInRect(m_ptMousePosition))
		{
			::SetCursor(m_hCursor);
			return TRUE;
		}
	}
	return CWnd::OnSetCursor(pWnd, nHitTest, message);
}

void CTaskbarNotifier::OnTimer(UINT nIDEvent)
{
	switch (nIDEvent)
	{
	case IDT_APPEARING:
		m_nAnimStatus = IDT_APPEARING;
		switch (m_nTaskbarPlacement)
		{
		case ABE_BOTTOM:
			if (m_nCurrentHeight < m_nBitmapHeight)
			{
				m_nCurrentPosY -= m_nIncrementShow;
				m_nCurrentHeight += m_nIncrementShow;
			}
			else
			{
				KillTimer(IDT_APPEARING);
				SetTimer(IDT_WAITING, m_dwTimeToStay, NULL);
				m_nAnimStatus = IDT_WAITING;
			}
			break;

		case ABE_TOP:
			if (m_nCurrentHeight < m_nBitmapHeight)
				m_nCurrentHeight += m_nIncrementShow;
			else
			{
				KillTimer(IDT_APPEARING);
				SetTimer(IDT_WAITING, m_dwTimeToStay, NULL);
				m_nAnimStatus = IDT_WAITING;
			}
			break;

		case ABE_LEFT:
			if (m_nCurrentWidth < m_nBitmapWidth)
				m_nCurrentWidth += m_nIncrementShow;
			else
			{
				KillTimer(IDT_APPEARING);
				SetTimer(IDT_WAITING, m_dwTimeToStay, NULL);
				m_nAnimStatus = IDT_WAITING;
			}
			break;

		case ABE_RIGHT:
			if (m_nCurrentWidth < m_nBitmapWidth)
			{
				m_nCurrentPosX -= m_nIncrementShow;
				m_nCurrentWidth += m_nIncrementShow;
			}
			else
			{
				KillTimer(IDT_APPEARING);
				SetTimer(IDT_WAITING, m_dwTimeToStay, NULL);
				m_nAnimStatus = IDT_WAITING;
			}
			break;
		}
		SetWindowPos(&wndTopMost, m_nCurrentPosX, m_nCurrentPosY, m_nCurrentWidth, m_nCurrentHeight, SWP_NOACTIVATE);
		break;

	case IDT_WAITING:
		KillTimer(IDT_WAITING);
		if (!m_modal) SetTimer(IDT_DISAPPEARING, m_dwHideEvents, NULL);
		break;

	case IDT_DISAPPEARING:
		m_nAnimStatus = IDT_DISAPPEARING;
		switch (m_nTaskbarPlacement)
		{
		case ABE_BOTTOM:
			if (m_nCurrentHeight > 0)
			{
				m_nCurrentPosY += m_nIncrementHide;
				m_nCurrentHeight -= m_nIncrementHide;
			}
			else
			{
				KillTimer(IDT_DISAPPEARING);
				Hide();
			}
			break;

		case ABE_TOP:
			if (m_nCurrentHeight > 0)
				m_nCurrentHeight -= m_nIncrementHide;
			else
			{
				KillTimer(IDT_DISAPPEARING);
				Hide();
			}
			break;

		case ABE_LEFT:
			if (m_nCurrentWidth > 0)
				m_nCurrentWidth -= m_nIncrementHide;
			else
			{
				KillTimer(IDT_DISAPPEARING);
				Hide();
			}
			break;

		case ABE_RIGHT:
			if (m_nCurrentWidth > 0)
			{					 
				m_nCurrentPosX += m_nIncrementHide;
				m_nCurrentWidth -= m_nIncrementHide;
			}
			else
			{
				KillTimer(IDT_DISAPPEARING);
				Hide();
			}
			break;
		}
		SetWindowPos(&wndTopMost, m_nCurrentPosX, m_nCurrentPosY, m_nCurrentWidth, m_nCurrentHeight, SWP_NOACTIVATE);
		break;
	}

	CWnd::OnTimer(nIDEvent);
}
